// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// MSVC++ requires this to be set before any other includes to get M_PI.
#define _USE_MATH_DEFINES

#include "ui/events/gesture_detection/motion_event_generic.h"

#include <cmath>
#include <utility>

#include "base/logging.h"
#include "ui/events/base_event_utils.h"

namespace ui {

PointerProperties::PointerProperties()
    : PointerProperties(0, 0, 0)
{
}

PointerProperties::PointerProperties(float x, float y, float touch_major)
    : id(0)
    , tool_type(MotionEvent::TOOL_TYPE_UNKNOWN)
    , x(x)
    , y(y)
    , raw_x(x)
    , raw_y(y)
    , pressure(0)
    , touch_major(touch_major)
    , touch_minor(0)
    , orientation(0)
    , tilt(0)
    , source_device_id(0)
{
}

PointerProperties::PointerProperties(const MotionEvent& event,
    size_t pointer_index)
    : id(event.GetPointerId(pointer_index))
    , tool_type(event.GetToolType(pointer_index))
    , x(event.GetX(pointer_index))
    , y(event.GetY(pointer_index))
    , raw_x(event.GetRawX(pointer_index))
    , raw_y(event.GetRawY(pointer_index))
    , pressure(event.GetPressure(pointer_index))
    , touch_major(event.GetTouchMajor(pointer_index))
    , touch_minor(event.GetTouchMinor(pointer_index))
    , orientation(event.GetOrientation(pointer_index))
    , tilt(event.GetTilt(pointer_index))
    , source_device_id(0)
{
}

void PointerProperties::SetAxesAndOrientation(float radius_x,
    float radius_y,
    float rotation_angle_degree)
{
    DCHECK(!touch_major && !touch_minor && !orientation);
    float rotation_angle_rad = rotation_angle_degree * M_PI / 180.f;
    DCHECK_GE(radius_x, 0) << "Unexpected x-radius < 0 (" << radius_x << ")";
    DCHECK_GE(radius_y, 0) << "Unexpected y-radius < 0 (" << radius_y << ")";
    DCHECK(0 <= rotation_angle_rad && rotation_angle_rad < M_PI)
        << "Unexpected touch rotation angle " << rotation_angle_rad << " rad";

    // Make the angle acute to ease subsequent logic. The angle range effectively
    // changes from [0, pi) to [0, pi/2).
    if (rotation_angle_rad >= M_PI_2) {
        rotation_angle_rad -= static_cast<float>(M_PI_2);
        std::swap(radius_x, radius_y);
    }

    if (radius_x > radius_y) {
        // The case radius_x == radius_y is omitted from here on purpose: for
        // circles, we want to pass the angle (which could be any value in such
        // cases but always seem to be set to zero) unchanged.
        touch_major = 2.f * radius_x;
        touch_minor = 2.f * radius_y;
        orientation = rotation_angle_rad - M_PI_2;
    } else {
        touch_major = 2.f * radius_y;
        touch_minor = 2.f * radius_x;
        orientation = rotation_angle_rad;
    }
}

MotionEventGeneric::MotionEventGeneric(Action action,
    base::TimeTicks event_time,
    const PointerProperties& pointer)
    : action_(action)
    , event_time_(event_time)
    , unique_event_id_(ui::GetNextTouchEventId())
    , action_index_(0)
    , button_state_(0)
    , flags_(0)
{
    PushPointer(pointer);
}

MotionEventGeneric::MotionEventGeneric(const MotionEventGeneric& other)
    : action_(other.action_)
    , event_time_(other.event_time_)
    , unique_event_id_(other.unique_event_id_)
    , action_index_(other.action_index_)
    , button_state_(other.button_state_)
    , flags_(other.flags_)
    , pointers_(other.pointers_)
{
    const size_t history_size = other.GetHistorySize();
    for (size_t h = 0; h < history_size; ++h)
        PushHistoricalEvent(other.historical_events_[h]->Clone());
}

MotionEventGeneric::~MotionEventGeneric()
{
}

uint32_t MotionEventGeneric::GetUniqueEventId() const
{
    return unique_event_id_;
}

MotionEvent::Action MotionEventGeneric::GetAction() const
{
    return action_;
}

int MotionEventGeneric::GetActionIndex() const
{
    DCHECK(action_ == ACTION_POINTER_DOWN || action_ == ACTION_POINTER_UP);
    DCHECK_GE(action_index_, 0);
    DCHECK_LT(action_index_, static_cast<int>(pointers_->size()));
    return action_index_;
}

size_t MotionEventGeneric::GetPointerCount() const
{
    return pointers_->size();
}

int MotionEventGeneric::GetPointerId(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].id;
}

float MotionEventGeneric::GetX(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].x;
}

float MotionEventGeneric::GetY(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].y;
}

float MotionEventGeneric::GetRawX(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].raw_x;
}

float MotionEventGeneric::GetRawY(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].raw_y;
}

float MotionEventGeneric::GetTouchMajor(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].touch_major;
}

float MotionEventGeneric::GetTouchMinor(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].touch_minor;
}

float MotionEventGeneric::GetOrientation(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].orientation;
}

float MotionEventGeneric::GetPressure(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].pressure;
}

float MotionEventGeneric::GetTilt(size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].tilt;
}

MotionEvent::ToolType MotionEventGeneric::GetToolType(
    size_t pointer_index) const
{
    DCHECK_LT(pointer_index, pointers_->size());
    return pointers_[pointer_index].tool_type;
}

int MotionEventGeneric::GetButtonState() const
{
    return button_state_;
}

int MotionEventGeneric::GetFlags() const
{
    return flags_;
}

base::TimeTicks MotionEventGeneric::GetEventTime() const
{
    return event_time_;
}

size_t MotionEventGeneric::GetHistorySize() const
{
    return historical_events_.size();
}

base::TimeTicks MotionEventGeneric::GetHistoricalEventTime(
    size_t historical_index) const
{
    DCHECK_LT(historical_index, historical_events_.size());
    return historical_events_[historical_index]->GetEventTime();
}

float MotionEventGeneric::GetHistoricalTouchMajor(
    size_t pointer_index,
    size_t historical_index) const
{
    DCHECK_LT(historical_index, historical_events_.size());
    return historical_events_[historical_index]->GetTouchMajor(pointer_index);
}

float MotionEventGeneric::GetHistoricalX(size_t pointer_index,
    size_t historical_index) const
{
    DCHECK_LT(historical_index, historical_events_.size());
    return historical_events_[historical_index]->GetX(pointer_index);
}

float MotionEventGeneric::GetHistoricalY(size_t pointer_index,
    size_t historical_index) const
{
    DCHECK_LT(historical_index, historical_events_.size());
    return historical_events_[historical_index]->GetY(pointer_index);
}

// static
scoped_ptr<MotionEventGeneric> MotionEventGeneric::CloneEvent(
    const MotionEvent& event)
{
    bool with_history = true;
    return make_scoped_ptr(new MotionEventGeneric(event, with_history));
}

// static
scoped_ptr<MotionEventGeneric> MotionEventGeneric::CancelEvent(
    const MotionEvent& event)
{
    bool with_history = false;
    scoped_ptr<MotionEventGeneric> cancel_event(
        new MotionEventGeneric(event, with_history));
    cancel_event->set_action(ACTION_CANCEL);
    cancel_event->set_unique_event_id(ui::GetNextTouchEventId());
    return cancel_event;
}

size_t MotionEventGeneric::PushPointer(const PointerProperties& pointer)
{
    DCHECK_EQ(0U, GetHistorySize());
    pointers_->push_back(pointer);
    return pointers_->size() - 1;
}

void MotionEventGeneric::RemovePointerAt(size_t index)
{
    DCHECK_LT(index, pointers_->size());
    pointers_->erase(pointers_->begin() + index);
}

void MotionEventGeneric::PushHistoricalEvent(scoped_ptr<MotionEvent> event)
{
    DCHECK(event);
    DCHECK_EQ(event->GetAction(), ACTION_MOVE);
    DCHECK_EQ(event->GetPointerCount(), GetPointerCount());
    DCHECK_EQ(event->GetAction(), GetAction());
    DCHECK_LE(event->GetEventTime().ToInternalValue(),
        GetEventTime().ToInternalValue());
    historical_events_.push_back(std::move(event));
}

MotionEventGeneric::MotionEventGeneric()
    : action_(ACTION_NONE)
    , unique_event_id_(ui::GetNextTouchEventId())
    , action_index_(-1)
    , button_state_(0)
{
}

MotionEventGeneric::MotionEventGeneric(const MotionEvent& event,
    bool with_history)
    : action_(event.GetAction())
    , event_time_(event.GetEventTime())
    , unique_event_id_(event.GetUniqueEventId())
    , action_index_(
          (action_ == ACTION_POINTER_UP || action_ == ACTION_POINTER_DOWN)
              ? event.GetActionIndex()
              : 0)
    , button_state_(event.GetButtonState())
    , flags_(event.GetFlags())
{
    const size_t pointer_count = event.GetPointerCount();
    for (size_t i = 0; i < pointer_count; ++i)
        PushPointer(PointerProperties(event, i));

    if (!with_history)
        return;

    const size_t history_size = event.GetHistorySize();
    for (size_t h = 0; h < history_size; ++h) {
        scoped_ptr<MotionEventGeneric> historical_event(new MotionEventGeneric());
        historical_event->set_action(ACTION_MOVE);
        historical_event->set_event_time(event.GetHistoricalEventTime(h));
        for (size_t i = 0; i < pointer_count; ++i) {
            historical_event->PushPointer(
                PointerProperties(event.GetHistoricalX(i, h),
                    event.GetHistoricalY(i, h),
                    event.GetHistoricalTouchMajor(i, h)));
        }
        PushHistoricalEvent(std::move(historical_event));
    }
}

MotionEventGeneric& MotionEventGeneric::operator=(
    const MotionEventGeneric& other)
{
    action_ = other.action_;
    event_time_ = other.event_time_;
    unique_event_id_ = other.unique_event_id_;
    action_index_ = other.action_index_;
    button_state_ = other.button_state_;
    flags_ = other.flags_;
    pointers_ = other.pointers_;
    const size_t history_size = other.GetHistorySize();
    for (size_t h = 0; h < history_size; ++h)
        PushHistoricalEvent(other.historical_events_[h]->Clone());
    return *this;
}

void MotionEventGeneric::PopPointer()
{
    DCHECK_GT(pointers_->size(), 0U);
    pointers_->pop_back();
}

} // namespace ui
